package com.csaba.connector;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.csaba.connector.model.Session;
import com.csaba.connector.service.DummyService;
import com.csaba.connector.service.LoginService;
import com.csaba.util.LoggerHelper;

/**
 *
 * This class is responsible for keeping session alive, where there is no 
 * activities. Sessions kept alive through {@link DummyService} if it is 
 * supported by the connector.
 * <p> The connectors may specify the dummy frequency through the {@link Session} object.
 * <p> This is a singleton. You can get reference to the server through {@link #getInstance()} method.
 * Normally the {@link LoginService} implementations add session to the DummyServer for monitoring.
 *
 * @author Gabe
 */
public class DummyServer
{
	private final static int CHECK_INTERVAL = 5; //checking interval in seconds.
	private final static Logger logger = LoggerHelper.getDefaultLogger();

	private static DummyServer server = new DummyServer();

	public static DummyServer getInstance()
	{
		return server;
	}

	private final List<Session> sessions = new ArrayList<Session>();

	private ServerThread serverThread;

	public void addSession( final Session session )
	{
		if ( session == null || session.getDummyInterval() < 1 )
		{ // do nothing: no dummy is required.
			return;
		}

		int size = 0;
		synchronized ( sessions )
		{
			if ( !sessions.contains(session) )
				sessions.add(session);
			size = sessions.size();
		}

		if ( size > 0 && serverThread == null )
		{
			//start thread
			logger.fine("Start dummy thread.");
			final Thread thread = new Thread(serverThread = new ServerThread());
			thread.start();
		}
	}

	public void removeSession( final Session session )
	{
		int size = 0;
		synchronized ( sessions )
		{
			sessions.remove(session);
			size = sessions.size();
		}

		if ( size == 0 && serverThread != null )
		{
			//stop thread
			logger.fine("Stop dummy thread.");
			serverThread.setActive(false);
			serverThread = null;
		}
	}

	class ServerThread implements Runnable
	{
		private boolean active = true;
		private final Map<Session, Long> lastRuns = new HashMap<Session, Long>();

		@Override
		public void run()
		{
			while ( active )
			{
				try
				{
					Thread.sleep(CHECK_INTERVAL * 1000);
				}
				catch ( final Exception ex )
				{
					logger.info("Dummy thread is interrupted: " + ex);
					active = false;
					break;
				}

				//creating snapshot of the sessions list to minimize the synchronised block
				List<Session> snapshot;

				synchronized ( sessions )
				{
					snapshot = new ArrayList<Session>(sessions.size());
					snapshot.addAll(sessions);
				}

				//clean hashmap
				for ( final Iterator<Session> si = lastRuns.keySet().iterator(); si.hasNext(); )
				{
					final Session session = si.next();
					if ( !snapshot.contains(session) )
						si.remove();
				}

				//check whether execution is required
				for ( final Session session : snapshot )
				{
					if ( !session.isActiveSession() )
					{
						//session was invalidated in the meantime.
						removeSession(session);
						continue;
					}

					if ( lastRuns.containsKey(session) )
					{
						final long lastRun = lastRuns.get(session);
						final long delayInSec = ( System.currentTimeMillis() - lastRun ) / 1000;
						if ( delayInSec >= session.getDummyInterval() )
						{
							//XXX this should be sent in seperate threads to avoid long dummys causing timeouts on other sessions.
							try
							{
								final DummyService service = BankServiceFactory.getBankService(session.getBank(),
										DummyService.class);

								service.execute(session);
							}
							catch ( final ServiceException ex )
							{
								logger.log(Level.SEVERE, "Failed to send dummy for session: " + session, ex);
							}

							lastRuns.put(session, System.currentTimeMillis());
						}
					}
					else
					{
						//at the first run, no dummy is sent for the sessions.
						lastRuns.put(session, System.currentTimeMillis());
					}
				}
			}
		}

		public void setActive( final boolean active )
		{
			this.active = active;
		}

		public boolean isActive()
		{
			return active;
		}

	}

}
